package com.tianji.remark.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.api.dto.remark.LikeTimesQueryDTO;
import com.tianji.api.dto.remark.LikedTimesDTO;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.constants.RedisConstant;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.StringUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.remark.domain.dto.LikeRecordFormDTO;
import com.tianji.remark.domain.dto.LikeTimeQueryDTO;
import com.tianji.remark.domain.po.LikedRecord;
import com.tianji.remark.mapper.LikedRecordMapper;
import com.tianji.remark.service.ILikedRecordService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.StringRedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>
 * 点赞记录表 服务实现类
 * </p>
 *
 * @author xjs
 */
@Slf4j
@Service
public class LikedRecordServiceImpl extends ServiceImpl<LikedRecordMapper, LikedRecord> implements ILikedRecordService {

    @Autowired
    private RabbitMqHelper mqHelper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    private static final String[] BIZTYPES = new String[]{"QA", "NOTE"};

    @Override
    public void addLikeRecord(LikeRecordFormDTO dto) {
        Long userId = UserContext.getUser();
        // 构造set的key
        String userLikeKey = buildUserLikeKey(dto.getBizType(), dto.getBizId());
        // 如果是点赞
        if(dto.getLiked()){
            // 添加到redis的set
            Long added = redisTemplate.opsForSet().add(userLikeKey, "" + userId);
            if(added == null || added <= 0){
                throw new BizIllegalException("不能重复点赞");
            }else{
                // do nothing, 已经添加到了redis中的set
            }
        }else{
            // 如果是取消点赞
            Long removed = redisTemplate.opsForSet().remove(userLikeKey, "" + userId);
            if(removed != null && removed <= 0){
                throw new BizIllegalException("没点过赞，不能取消");
            }
            // do nothing, 已经从redis中的set删除
        }
        // 获取总数
        Long count = redisTemplate.opsForSet().size(userLikeKey);
        // 修改总数，redis中的zset，大key: bizType  小key: bizId  score: 总点赞次数
        String likeTotalKey = buildLikeTotalKey(dto.getBizType());
        redisTemplate.opsForZSet().add(likeTotalKey, ""+dto.getBizId(), count);
    }

    @Override
    public void syncLikeCountToDB() {
        for(String bizType : BIZTYPES){
            log.info("syncLikeCountToDB, bizType:{} start", bizType);
            // 构造消息体
            List<LikedTimesDTO> msgList = new ArrayList<>();
            String likeTotalKey = buildLikeTotalKey(bizType);
            while(true){
                // 从zset中弹出
                Set<ZSetOperations.TypedTuple<String>> typedTuples = redisTemplate.opsForZSet().popMin(likeTotalKey, 100);
                if(CollUtils.isEmpty(typedTuples)){
                    break;
                }
                log.info("syncLikeCountToDB, bizType:{}, count:{}", bizType, typedTuples.size());
                // 构造消息列表
                for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
                    String value = typedTuple.getValue();
                    Double score = typedTuple.getScore();
                    msgList.add(LikedTimesDTO.of(Long.parseLong(value), score.intValue()));
                }
                // 构造消息发送的routing key
                String key = StringUtils.format(MqConstants.Key.LIKED_TIMES_KEY_TEMPLATE, bizType);
                // 发送消息
                mqHelper.send(MqConstants.Exchange.LIKE_RECORD_EXCHANGE, key, msgList);
            }
        }
    }


    //@Override
    public void addLikeRecordOld(LikeRecordFormDTO dto) {
        // 先查询 不存在就抛异常
        LikedRecord record = this.lambdaQuery()
                .eq(LikedRecord::getUserId, UserContext.getUser())
                .eq(LikedRecord::getBizType, dto.getBizType())
                .eq(LikedRecord::getBizId, dto.getBizId())
                .one();
        // 如果是点赞
        if(dto.getLiked()){
            // 说明已经点过赞了
            if(record != null){
                throw new BizIllegalException("不能重复点赞");
            }else{
                // 记录数据库
                LikedRecord insert = BeanUtils.copyBean(dto, LikedRecord.class);
                insert.setUserId(UserContext.getUser());
                save(insert);
            }
        }else{
            if(record == null){
                throw new BizIllegalException("没点过赞，不能取消");
            }else{
                // 取消 -> 删除
                this.removeById(record.getId());
            }
        }
        // 查询点赞的次数
        Integer count = this.lambdaQuery()
                .eq(LikedRecord::getBizType, dto.getBizType())
                .eq(LikedRecord::getBizId, dto.getBizId()).count();
        // 构造消息发送的routing key
        String key = StringUtils.format(MqConstants.Key.LIKED_TIMES_KEY_TEMPLATE, dto.getBizType());
        // 构造消息体
        LikedTimesDTO likedTimesDTO = new LikedTimesDTO();
        likedTimesDTO.setBizId(dto.getBizId());
        likedTimesDTO.setLikedTimes(count);
        // 发送消息
        mqHelper.send(MqConstants.Exchange.LIKE_RECORD_EXCHANGE, key, likedTimesDTO);
    }

    /**
     * 从redis查询
     * */
    @Override
    public LikeTimesQueryDTO queryByBizTypeAndIds(LikeTimesQueryDTO dto) {
        if(StringUtils.isEmpty(dto.getBizType()) || CollUtils.isEmpty(dto.getBizIds())){
            log.error("bizType 和 bizIds 不能为空");
            return null;
        }
        // userId
        final Long userId = UserContext.getUser();
        // 批量发送到redis执行
        List<Object> objects = redisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                StringRedisConnection conn = (StringRedisConnection) connection;
                for (Long bizId : dto.getBizIds()) {
                    String userLikeKey = buildUserLikeKey(dto.getBizType(), bizId);
                    return conn.sIsMember(userLikeKey, "" + userId);
                }
                return null;
            }
        });
        // 构造返回结果
        List<Long> totalBizIds = dto.getBizIds();
        List<Long> likedBizIds = new ArrayList<>();
        // 遍历执行结果
        for(int i=0; i<objects.size(); i++){
            Object o = objects.get(i);
            // 获取执行结果中是true的索引
            if(o != null && Boolean.valueOf(o.toString())){
                Long bizId = totalBizIds.get(i);
                likedBizIds.add(bizId);
            }
        }
        // 返回
        return new LikeTimesQueryDTO(dto.getBizType(), likedBizIds);
    }

    /**
     * 使用mysql多字段in查询
     * */
    //@Override
    public LikeTimesQueryDTO queryByBizTypeAndIdsSQL(LikeTimesQueryDTO dto) {

        if(StringUtils.isEmpty(dto.getBizType()) || CollUtils.isEmpty(dto.getBizIds())){
            log.error("bizType 和 bizIds 不能为空");
            return null;
        }
        // userId
        Long userId = UserContext.getUser();
        // 多字段in查询
        List<LikeTimeQueryDTO> dtos = new ArrayList<>();
        for (Long bizId : dto.getBizIds()) {
            LikeTimeQueryDTO query = new LikeTimeQueryDTO();
            query.setBizType(dto.getBizType());
            query.setBizId(bizId);
            dtos.add(query);
        }
        dtos = this.baseMapper.queryByBizTypeAndIds(userId, dtos);
        if(CollUtils.isEmpty(dtos)){
            return null;
        }
        LikeTimesQueryDTO ret = new LikeTimesQueryDTO();
        ret.setBizType(dto.getBizType());
        ret.setBizIds(dtos.stream().map(LikeTimeQueryDTO::getBizId).collect(Collectors.toList()));
        return ret;
    }

    /**
     * 使用mp
     * */
    public LikeTimesQueryDTO queryByBizTypeAndIdsUseMP(LikeTimesQueryDTO dto) {
        // userId
        Long userId = UserContext.getUser();
        // 查询数据库
        LambdaQueryWrapper<LikedRecord> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(LikedRecord::getUserId, userId);
        queryWrapper.eq(LikedRecord::getBizType, dto.getBizType());
        queryWrapper.in(LikedRecord::getBizId, dto.getBizIds());
        List<LikedRecord> records = this.list(queryWrapper);
        if(CollUtils.isEmpty(records)){
            return null;
        }
        LikeTimesQueryDTO ret = new LikeTimesQueryDTO();
        ret.setBizType(dto.getBizType());
        ret.setBizIds(records.stream().map(LikedRecord::getBizId).collect(Collectors.toList()));
        return ret;
    }

    private String buildUserLikeKey(String bizType, Long bizId){
        return StringUtils.format(RedisConstant.REMARK_USER_LIKE_SET, bizType, bizId);
    }

    private String buildLikeTotalKey(String bizType){
        return StringUtils.format(RedisConstant.REMARK_LIKE_TOTAL_ZSET, bizType);
    }

}
